package com.efrei.uchat.client.gui.control;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import org.apache.log4j.Logger;

import com.efrei.uchat.client.UChatClient;
import com.efrei.uchat.client.UChatUI;
import com.efrei.uchat.client.gui.conf.ServerConfig;
import com.efrei.uchat.client.gui.view.CroomRequest;
import com.efrei.uchat.client.gui.view.CroomTab;
import com.efrei.uchat.client.gui.view.DefaultTab;
import com.efrei.uchat.client.gui.view.LoginRequestTab;
import com.efrei.uchat.client.gui.view.LoginTab;
import com.efrei.uchat.client.gui.view.PasswordRequest;
import com.efrei.uchat.client.gui.view.UChatWindow;
import com.efrei.uchat.client.model.Croom;
import com.efrei.uchat.client.model.Message;
import com.efrei.uchat.message.CroomMessage;
import com.efrei.uchat.message.CroomUsersMessage;
import com.efrei.uchat.message.CroomsMessage;
import com.efrei.uchat.message.LoginMessage;
import com.efrei.uchat.message.TextMessage;
import com.efrei.uchat.model.croom.CroomMessageStatus;
import com.efrei.uchat.utils.Tools;

public class UChatControl extends UChatUI implements ActionListener {
	
	private static Logger logger = Logger.getLogger(UChatControl.class);

	private final UChatControl context = this;
	
	/*
	 * login fields
	 */
	
	private ServerConfig config;
	
	/*
	 * UI fields
	 */

	private UChatWindow window;
	
	// selected status
	public enum TabSelected {
		UNKNOWN,
		LOGIN,
		LOGIN_FAILED,
		DEFAULT,
		CROOM
	};
	private TabSelected selection = TabSelected.UNKNOWN;
	
	/**
	 * tab selected reference
	 */
	private LoginTab loginTab;
	private LoginRequestTab loginRequest;
	
	private DefaultTabControl defaultController;
	/**
	 * association Croom => controller with :
	 * <ul>
	 * <li>null: unknown croom, need {@link #joinCroom(String) joinCroom}</li>
	 * <li>value.getTab()==null: tab not created, see
	 * {@link #croomResult(CroomMessage) croomResult} and
	 *  {@link CroomTabControl#setTab(javax.swing.JPanel) setTab}</li>
	 * </ul>
	 */
	private Map<Croom, CroomTabControl> croomsControllers;
	
	public UChatControl(String confFile) {
		super();
		config = new ServerConfig(confFile);
		window = new UChatWindow();
		window.setController(this);
		setLoginTab();
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		switch (selection) {
		case LOGIN:
			{
				// login step
				if(e.getSource()==loginTab.getBtnSubmit()) {
					String host = loginTab.getTextHost().getText();
					String pseudo =  loginTab.getTextPseudo().getText();
					int port;
					try {
						port =  Integer.valueOf(loginTab.getTextPort().getText());
					} catch(NumberFormatException ex) {
						error("Illegal port", "Illegal port ! Indicate a number");
						return;
					}
					config.setHost(host);
					config.setPort(port);
					config.setPseudo(pseudo);
					logger.info("Send login request for "+config);
					try {
						login(host, port, pseudo);
					} catch (IOException ex) {
						logger.error("error with server connection", ex);
						error("Server not found", "Error with connexion: "+ex.getMessage());
					}
				}
			}
			break;
		case LOGIN_FAILED:
			{
				// deprecated area
				if(e.getSource()==loginRequest.getBtnSubmit()) {
					loginRequest(loginRequest.getTxtLogin().getText());
				}
			}
			break;
		default:
			// DEFAULT or CROOM
			if(e.getSource()==window.getMntmConnexion()) {
				// deconnexion
				disconnect(false);
			} else if(e.getSource()==window.getMntmInfos()) {
				// display infos
				JOptionPane.showMessageDialog(window, String.format(
						"Login: %s\nServer: %s:%s",
						config.getPseudo(),
						config.getHost(),
						config.getPort()));
			}
			break;
		}
	}
	
	private void error(final String title, final String text) {
		SwingUtilities.invokeLater(new Runnable() {
			
			@Override
			public void run() {
				JOptionPane.showMessageDialog(window,
						text,
						title,
				        JOptionPane.ERROR_MESSAGE);
			}
		});
	}
	
	private void login(String host, int port, String pseudo) throws IOException {
		// TODO: display wait message
		logger.info("create new UChatClient and send login request");
		// try to connect to server
		UChatClient uChatClient = new UChatClient(host, port);
		setUChatClient(uChatClient);
		// send first login request
		loginRequest(pseudo);
	}
	
	/*
	 * SELECTED TAB PRIVATE
	 */
	
	private void setTabSelected(LoginTab tab) {
		this.selection = TabSelected.LOGIN;
		this.loginTab = tab;
	}
	
	/*
	private void setTabSelected(LoginRequestTab tab) {
		this.selection = TabSelected.LOGIN_FAILED;
		this.loginRequest = tab;
	}
	*/
	
	private void setTabSelected(DefaultTabControl control) {
		this.selection = TabSelected.DEFAULT;
	}
	
	private void setTabSelected(CroomTabControl control) {
		this.selection = TabSelected.CROOM;
	}

	/*
	 * SELECTED TAB PUBLIC
	 */
	
	private void setLoginTab() {
		if(selection==TabSelected.DEFAULT || selection==TabSelected.CROOM) {
			logger.info("Last selection: "+selection+" and setLoginTab called");
		}
		selection = TabSelected.UNKNOWN;
		window.getMntmConnexion().setText("Connexion");
		// construct window and set values to loginTab
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				logger.info("BEGIN construct login tab");
				
				// build loginPane
				LoginTab loginTab = new LoginTab();
				loginTab.addController(context);
				loginTab.getTextHost().setText(config.getHost());
				loginTab.getTextPort().setText(String.valueOf(config.getPort()));
				loginTab.getTextPseudo().setText(config.getPseudo());

				// add to tabs
				window.getTabbedPane().removeAll();
				window.getTabbedPane().addTab("Login", loginTab);
				
				context.setTabSelected(loginTab);
				logger.info("login tab completed");
			}
		});
	}
	
	/*
	 * login failed dropped
	private void setLoginFailedTab() {
		selection = TabSelected.UNKNOWN;
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				logger.info("BEGIN construct login failed tab");
				// build loginPane
				LoginRequestTab tab = new LoginRequestTab();
				tab.addController(context);

				// add to tabs
				window.getTabbedPane().removeAll();
				window.getTabbedPane().addTab("Select login", tab);

				context.setTabSelected(tab);
				logger.info("login failed tab completed");
			}
		});
	}
	*/

	public void setDefaultTab() {
		if(selection==TabSelected.LOGIN || selection==TabSelected.LOGIN_FAILED) {
			// request for crooms list
			logger.info("UChatControl: reset croomsControllers");
			croomsControllers = new LinkedHashMap<Croom, CroomTabControl>();
			getCroomsList();
		}
		selection = TabSelected.UNKNOWN;
		window.getMntmConnexion().setText("Deconnexion");
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				logger.info("BEGIN construct default tab");
				if(defaultController==null) {
					// build loginPane
					defaultController = new DefaultTabControl(context);
					DefaultTab tab = new DefaultTab();
					tab.addController(context);
					defaultController.setTab(tab);
					
					// add to tabs
					window.getTabbedPane().removeAll();
					window.getTabbedPane().addTab("Select chat rooms", tab);
	
					context.setTabSelected(defaultController);
					logger.info("default tab completed");
				} else {
					logger.info("switch to default tab");
					window.getTabbedPane().setSelectedIndex(0);
				}
			}
		});
	}
	
	public void setCroomTab(Croom croom) {
		if(croomsControllers.get(croom)==null) {
			// subscribe to croom
			joinCroom(croom.getTopic(), croom.getPassword());
		} else {
			displayCroomTab(croom);
		}
	}
	
	private void displayCroomTab(final Croom croom) {
		selection = TabSelected.UNKNOWN;
		
		logger.info("displayCroomTab for "+croomsControllers.get(croom));
		final CroomTabControl controller = croomsControllers.get(croom);
		
		// and get users for new croom
		if(croomsControllers.get(croom).getTab()==null
				|| croomsControllers.get(croom).getUsersModel().size()==0) {
			logger.info("New croom "+croom.getTopic()+", get users");
			this.getCroomUsers(croom.getTopic());
		}
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				if(controller.getTab()==null) {
					logger.info("BEGIN construct croom tab for "+croom.getTopic());
					// build loginPane
					CroomTab tab = new CroomTab(croom.getTopic());
					controller.setTab(tab);
					// add to tabs
					window.getTabbedPane().addTab(croom.getTopic(), tab);
				}

				logger.info("croom tab "+croom+" completed and selected");
				context.setTabSelected(controller);
				window.getTabbedPane().setSelectedComponent(controller.getTab());
			}
		});
	}

	@Override
	public void receivedLoginMessage(LoginMessage message) {
		if(message.isValid()) {
			config.setPseudo(message.getLogin());
			if(selection==TabSelected.LOGIN || selection==TabSelected.LOGIN_FAILED) {
				loginTab = null;
				loginRequest = null;
				setDefaultTab();
			}
		} else {
			error("Invalid login", "Login "+message.getLogin()+" already taken");
			// loginFailed not display, disconnect
			disconnect(false);
		}
		if(selection==TabSelected.LOGIN) {
			// update conf
			config.setHost(loginTab.getTextHost().getText());
			config.setPort(Integer.valueOf(loginTab.getTextPort().getText()));
			logger.info("Update "+config);
		}
	}

	@Override
	public void receivedTextMessage(TextMessage message) {
		CroomTabControl control = croomsControllers.get(getCroom(message.getCroom()));
		if(control!=null) {
			logger.debug("add message to "+control.getCroom());
			control.addMessage(new Message(message));
		} else {
			logger.debug("Unknown croom "+message.getCroom());
		}
	}

	@Override
	public void receivedCroomList(CroomsMessage message) {
		defaultController.clearCrooms();
		for (Entry<String, Boolean> pair : message.getCrooms().entrySet()) {
			Croom croom;
			String topic = pair.getKey();
			// add croom if not known
			if((croom = getCroom(topic))==null) {
				// unknown croom
				logger.debug("add unknown croom "+topic+" in defaultTab");
				croom = new Croom(topic, pair.getValue());
			}
			defaultController.addCroom(croom);
		}
	}

	@Override
	public void receivedCroomUsers(CroomUsersMessage message) {
		CroomTabControl control = croomsControllers.get(getCroom(message.getCroom()));
		if(control==null) {
			// public exception
			logger.debug("create public tab");
			control = new CroomTabControl(context, new Croom(message.getCroom(), false));
		}
		control.setUsers(message.getUsers().keySet());
		
	}

	@Override
	public void croomResult(CroomMessage message) {
		if(message.getStatus()==CroomMessageStatus.SUCCESS) {
			logger.info(String.format("Join croom %s succeeded !", message.getCroom()));
			// croom already put in getCrooms
			Croom croom = getCroom(message.getCroom());
			if(croom==null) {
				croom = new Croom(message.getCroom(), false);
			}
			if(croomsControllers.get(croom)==null) {
				// create new controller
				logger.info("create new controller for "+message.getCroom());
				croomsControllers.put(croom, new CroomTabControl(context, croom));
			}
			// and display this tab
			setCroomTab(croom);
		} else {
			String title = "Error with croom "+message.getCroom();
			String text =
					(message.getStatus()==CroomMessageStatus.FORBIDDEN?
							"invalid password":"unknown error");
			error(title, title+": "+text);
			joinPrivateCroom(message.getCroom());
		}
	}
	
	@Override
	public void joinCroom(String croom) {
		if(getCroom(croom).isPrivate()) {
			joinPrivateCroom(croom);
		} else {
			super.joinCroom(croom);
		}
	}
	
	@Override
	public void joinCroom(String croom, String password) {
		Croom croomRef = getCroom(croom);
		if(password==null && croomRef!=null && croomRef.isPrivate()) {
			joinPrivateCroom(croom);
		} else {
			super.joinCroom(croom, password);
		}
	}
	
	private void joinPrivateCroom(final String croom) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				// request panel
				PasswordRequest tab = new PasswordRequest();
				
				int choose = JOptionPane.showConfirmDialog(window, tab,
						String.format(
								"Private chat room %s",
								croom),
						JOptionPane.OK_CANCEL_OPTION);
				if(choose==JOptionPane.OK_OPTION) {
					String password = String.valueOf(tab.getTxtPassword().getPassword());
					if(password==null || password.length()<1 || password.contains("#")) {
						error("Invalid password",
								"Invalid password, require not empty and not contains '#'");
					} else {
						joinCroom(croom, password);
					}
				}
			}
		});
	}

	@Override
	public void disconnect() {
		disconnect(false);
	}
	
	private void disconnect(boolean exit) {
		writeConfig();
		
		// reinit UI
		String t = (exit?"":" and reset window");
		logger.info("close connection"+t);
		setUChatClient(null);
		if(!exit)
			setLoginTab();
	}
	
	public void writeConfig() {
		// save config
		logger.info("Save "+config);
		try {
			config.save();
		} catch (Exception e) {
			logger.error("Error while config save", e);
			error("Error while writing conf", e.getMessage());
		}
	}

	public void exit() {
		logger.info("exit called");
		disconnect(true);
	}
	
	/**
	 * @param topic
	 * @return croom or null if not found
	 */
	public Croom getCroom(String topic) {
		for (Croom croom : croomsControllers.keySet()) {
			if(croom.getTopic().equals(topic)) {
				return croom;
			}
		}
		return null;
	}
	
	@Override
	public void leaveCroom(String croom) {
		super.leaveCroom(croom);
		int selected = window.getTabbedPane().getSelectedIndex();
		CroomTabControl control = croomsControllers.put(getCroom(croom), null);
		window.getTabbedPane().removeTabAt(selected);

		logger.info(String.format("leave croom %s and delete tab %s",
				control.getCroom(), selected));
		
		// and display DefaultTab
		setDefaultTab();
	}

	/**
	 * request user for new croom and send join request
	 */
	public void newCroom() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				// request panel
				CroomRequest tab = new CroomRequest();
				
				int choose = JOptionPane.showConfirmDialog(window, tab,
						"", JOptionPane.OK_CANCEL_OPTION);
				if(choose==JOptionPane.OK_OPTION) {
					String croom = tab.getTxtTopic().getText();
					String password = String.valueOf(tab.getTxtPassword().getPassword());
					if(croom==null || croom.length()<1 || croom.contains("#")) {
						error("Invalid Chat room",
								"Invalid chat room, require not empty and not contains '#'");
					} else {
						if(password.length()==0) {
							password = null;
						} else if(password.length()<1 || password.contains("#")) {
							error("Invalid password",
									"Invalid password, require not empty and not contains '#'");
						}
						logger.info(String.format("User create croom %s %s", croom, password));
						joinCroom(croom, password);
					}
				}
			}
		});
	}

	public static void main(String[] args) throws UnsupportedEncodingException {
		@SuppressWarnings("unused")
		Logger logger = Tools.initLog4jRootLogger();
		new UChatControl(new File(
				// program's path
				Tools.getExecutablePath(),
				// filename
				"client.cfg").getAbsolutePath());
	}
}
